package com.hra.logger.collector;

import android.text.TextUtils;
import android.util.Log;

import com.hra.logger.utils.Utils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

public class BaseLogCollector implements Runnable {
    protected final String TAG = getClass().getSimpleName();

    protected final String typeName;
    protected final String displayName;
    protected final String logCommand;

    private volatile boolean isFirstStartAfterReboot = false;
    private volatile boolean isRunning = false;

    private Process logProcess;
    private OutputStreamWriter writer;

    private int fileIndex = 1;
    private File logDir;
    private File typeLogDir;
    private String timestamp;
    private String filePrefix;

    public BaseLogCollector(String typeName, String displayName, String logCommand) {
        this.typeName = typeName;
        this.displayName = displayName;
        this.logCommand = logCommand;
    }

    /**
     * @return subSwitch isVisible
     */
    public boolean isVisible() {
        return true;
    }

    public void start() {
        if (Utils.DEBUG) {
            Log.d(TAG, "start: " + displayName + ", isFirstStartAfterReboot=" + isFirstStartAfterReboot);
        }
        isRunning = true;
        new Thread(this).start();
    }

    public void stop() {
        if (Utils.DEBUG) {
            Log.d(TAG, "stop: " + displayName);
        }
        isRunning = false;
        fileIndex = 1;
        if (logProcess != null) {
            logProcess.destroy();
        }
        closeWriter();
    }

    @Override
    public void run() {
        try {
            if (TextUtils.isEmpty(displayName) || TextUtils.isEmpty(logCommand)) return;

            // Create a new folder only on the first startup (non reboot)
            if (!isFirstStartAfterReboot) {
                timestamp = new SimpleDateFormat("yyyy_MMdd_HHmmss", Locale.getDefault()).format(new Date());

                if (Utils.NEW_DIR_STRUCTURE) {
                    // DIR: /sdcard/Logger/typeName/Log_timestamp/displayName_timestamp_index.log
                    File typeDir = new File(Utils.LOG_PATH, typeName);
                    if (!typeDir.exists() && !typeDir.mkdirs()) {
                        if (Utils.DEBUG) {
                            Log.w(TAG, "Failed to create type directory: " + typeDir.getAbsolutePath());
                        }
                    }
                    logDir = new File(typeDir, "Log_" + timestamp);
                } else {
                    // DIR: /sdcard/Logger/Log_timestamp/typeName/displayName_timestamp_index.log
                    logDir = new File(Utils.LOG_PATH, "Log_" + timestamp);
                }

                if (!logDir.exists() && !logDir.mkdirs()) {
                    if (Utils.DEBUG) {
                        Log.w(TAG, "Failed to create directory: " + logDir.getAbsolutePath());
                    }
                }
                filePrefix = displayName + "_" + timestamp;
                //Reset file index
                fileIndex = 1;
            } else {
                // Use existing folder after reboot
                findOrSetLogDir();
            }

            // Ensure that the type subdirectories exist
            if (!Utils.NEW_DIR_STRUCTURE) {
                typeLogDir = new File(logDir, typeName);
                if (!typeLogDir.exists() && !typeLogDir.mkdirs()) {
                    if (Utils.DEBUG) {
                        Log.w(TAG, "Failed to create type directory: " + typeLogDir.getAbsolutePath());
                    }
                }
            }

            if (Utils.DEBUG) {
                Log.w(TAG, "run: typeName=" + typeName + " displayName=" + displayName
                        + ", isFirstStartAfterReboot=" + isFirstStartAfterReboot);
            }

            if (isFirstStartAfterReboot) {
                // First startup after device restart: Reuse existing files
                findOrCreateLogFile();
                isFirstStartAfterReboot = false;
            } else {
                // Normal startup: always creating new files
                createNewLogFile();
            }

            logProcess = Runtime.getRuntime().exec(logCommand);
            BufferedReader reader = new BufferedReader(new InputStreamReader(logProcess.getInputStream()));

            if (Utils.DEBUG) {
                Log.w(TAG, "run: start, displayName = " + displayName);
            }
            while (isRunning) {
                String logLine = null;
                logLine = reader.readLine();

                if ("dmesg".equals(displayName)) {
                    if (logLine == null) break;
                }

                if (logLine != null) {
                    writer.write(logLine + "\n");
                    writer.flush();

                    // Check if it exceeds the maximum size
                    if (getCurrentLogFile().length() > Utils.MAX_FILE_SIZE) {
                        rotateLogFile();
                    }
                }
            }
            if (Utils.DEBUG) {
                Log.w(TAG, "run: end, displayName = " + displayName);
            }

            closeWriter();
            reader.close();
        } catch (IOException e) {
            Log.w(TAG, "Error in " + displayName + " collector: " + e.getMessage());
        } finally {
            if (logProcess != null) {
                logProcess.destroy();
            }
            closeWriter();
        }
    }

    private File currentLogFile;

    /**
     * Search for available log folders (only used after device restart)
     */
    private void findOrSetLogDir() {
        if (logDir != null && logDir.exists()) {
            // Skip if already set
            return;
        }

        File parentDir = new File(Utils.LOG_PATH);
        if (!parentDir.exists()) {
            return;
        }

        File[] logDirs;
        if (Utils.NEW_DIR_STRUCTURE) {
            // new dir structure：Search in the type directory
            File typeDir = new File(parentDir, typeName);
            if (!typeDir.exists()) {
                // Type directory does not exist, create a new one
                timestamp = new SimpleDateFormat("yyyy_MMdd_HHmmss", Locale.getDefault()).format(new Date());
                logDir = new File(typeDir, "Log_" + timestamp);
                if (!logDir.exists() && !logDir.mkdirs() && Utils.DEBUG) {
                    Log.w(TAG, "Failed to create directory: " + logDir.getAbsolutePath());
                }
                filePrefix = displayName + "_" + timestamp;
                return;
            }

            // Search for timestamp directory in the type directory
            logDirs = typeDir.listFiles((dir, name) -> name.startsWith("Log_"));
        } else {
            // Old dir structure: Search directly in the root directory
            logDirs = parentDir.listFiles((dir, name) -> name.startsWith("Log_"));
        }

        if (logDirs == null || logDirs.length == 0) {
            // Log folder not found, create a new folder
            timestamp = new SimpleDateFormat("yyyy_MMdd_HHmmss", Locale.getDefault()).format(new Date());

            if (Utils.NEW_DIR_STRUCTURE) {
                File typeDir = new File(parentDir, typeName);
                if (!typeDir.exists() && !typeDir.mkdirs() && Utils.DEBUG) {
                    Log.w(TAG, "Failed to create type directory: " + typeDir.getAbsolutePath());
                }
                logDir = new File(typeDir, "Log_" + timestamp);
            } else {
                logDir = new File(parentDir, "Log_" + timestamp);
            }

            if (!logDir.exists() && !logDir.mkdirs() && Utils.DEBUG) {
                Log.w(TAG, "Failed to create directory: " + logDir.getAbsolutePath());
            }
            filePrefix = displayName + "_" + timestamp;
            return;
        }

        // Sort by Last Modified Time (Latest First)
        Arrays.sort(logDirs, (d1, d2) -> Long.compare(d2.lastModified(), d1.lastModified()));
        logDir = logDirs[0];
        // Extract timestamp from folder name
        String dirName = logDir.getName();
        if (dirName.startsWith("Log_")) {
            timestamp = dirName.substring(4);
            filePrefix = displayName + "_" + timestamp;
        }
    }

    /**
     * Search for available log files (may reuse incomplete files after restart)
     */
    private void findOrCreateLogFile() throws IOException {
        // 1. Attempt to search for recently modified log files
        File latestFile = findLatestLogFile();
        // 2. Check if the file is available (existing and not full)
        if (latestFile != null && latestFile.exists() && latestFile.length() < Utils.MAX_FILE_SIZE) {
            Log.i(TAG, "Reusing existing log file: " + latestFile.getAbsolutePath());
            currentLogFile = latestFile;
            //Extract index from file name
            extractFileIndexFromName(latestFile.getName());
            //Open file in append mode
            writer = new OutputStreamWriter(new FileOutputStream(currentLogFile, true));
        } else {
            // 3. Create a new file if there are no available files
            createNewLogFile();
        }
    }

    /**
     * Extract file index from file name
     */
    private void extractFileIndexFromName(String fileName) {
        try {
            // File name format：displayName_timestamp_index.log
            int start = fileName.lastIndexOf('_') + 1;
            int end = fileName.lastIndexOf('.');
            String indexStr = fileName.substring(start, end);
            fileIndex = Integer.parseInt(indexStr);
        } catch (Exception e) {
            Log.e(TAG, "Failed to extract index from filename: " + fileName);
            // Reset index when error occurs
            fileIndex = 1;
        }
    }

    /**
     * Search for recently modified log files
     */
    private File findLatestLogFile() {
        File searchDir;
        if (Utils.NEW_DIR_STRUCTURE) {
            // new dir structure：Search directly in the timestamp directory
            searchDir = logDir;
        } else {
            // old dir structure：Search in the type subdirectories
            searchDir = typeLogDir;
        }

        if (searchDir == null || !searchDir.exists()) {
            return null;
        }

        File[] logFiles = searchDir.listFiles((dir, name) ->
                name.startsWith(displayName + "_") && name.endsWith(".log")
        );
        if (logFiles == null || logFiles.length == 0) {
            return null;
        }
        // Sort by Last Modified Time (Latest First)
        Arrays.sort(logFiles, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()));
        return logFiles[0];
    }

    private void createNewLogFile() throws IOException {
        File parentDir;
        if (Utils.NEW_DIR_STRUCTURE) {
            // new dir structure：The file is directly stored in the timestamp directory
            parentDir = logDir;
        } else {
            // old dir structure：Files are stored in the type subdirectories
            parentDir = typeLogDir;
            // Ensure that the type subdirectories exist
            if (!parentDir.exists() && !parentDir.mkdirs() && Utils.DEBUG) {
                Log.w(TAG, "Failed to create type directory: " + parentDir.getAbsolutePath());
            }
        }

        currentLogFile = new File(parentDir, filePrefix + "_" + fileIndex + ".log");
        writer = new OutputStreamWriter(new FileOutputStream(currentLogFile));
        if (Utils.DEBUG) {
            Log.i(TAG, "Created new log file: " + currentLogFile.getAbsolutePath());
        }
    }

    private void rotateLogFile() throws IOException {
        closeWriter();
        fileIndex++;
        createNewLogFile();
        if (Utils.DEBUG) {
            Log.i(TAG, "Rotated to new log file: " + currentLogFile.getName());
        }
    }

    public File getCurrentLogFile() {
        return currentLogFile;
    }

    private void closeWriter() {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                Log.e(TAG, "Error closing writer: " + e.getMessage());
            }
            writer = null;
        }
    }

    public void setFirstStartAfterReboot(boolean firstStartAfterReboot) {
        isFirstStartAfterReboot = firstStartAfterReboot;
    }
}
